package main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.domain;

import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.domain.utils.LexicoHelper.LETTERS_PATTERN;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.domain.utils.LexicoHelper.NUMBERS_PATTERN;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.domain.utils.LexicoHelper.isCharacter;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.AND;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.ASSIGN;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.CHARACTER;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.COMMA;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.EOF;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.EQUAL;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.EXCLAMATION;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.GREATER;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.GREATER_EQUAL;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.LEFT_BRACE;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.LEFT_BRACKET;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.LEFT_PARENTHESIS;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.LESS;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.LESS_EQUAL;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.MINUS;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.MULTIPLICATION;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.NOT_EQUAL;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.NUMBER;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.OR;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.PERIOD;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.PLUS;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.RIGHT_BRACE;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.RIGHT_BRACKET;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.RIGHT_PARENTHESIS;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.SEMICOLON;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.SLASH;
import static main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind.UNKNOWN;

import java.io.IOException;
import java.io.Reader;
import java.io.ObjectInputStream.GetField;
import java.io.UnsupportedEncodingException;
import java.nio.charset.CharacterCodingException;
import java.nio.charset.Charset;
import java.util.concurrent.ExecutionException;

import main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.domain.utils.LexicoHelper;
import main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.enumx.Kind;
import main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.exceptions.CharConErrorException;
import main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.exceptions.IntegerOverflowException;
import main.java.ufabc.edu.compiladores.javauino.analisadores.lexico.exceptions.LexicalErrorException;


public class Scanner {

	static Reader in;
	static char ch;
	static int line, col;
	static final int EOFCH = '\u0080';

	public static void init(Reader r) {
		in = r;
		line = 1;
		col = 0;
		nextCh();
	}

	private static void nextCh() {
		try {
			ch = (char) in.read();
			col++;
			if (ch == '\n') {
				line++;
				col = 0;
			} else if (ch == '\uffff') {
				ch = EOFCH;
			}
		} catch (IOException e) {
			ch = EOFCH;
		}
	}

	public static Token next() {
		// FIXME -- arrumar
		while (ch <= ' ' || ch <= '\t' || ch <= '\r' || ch <= '\n')
			nextCh();
		Token t = new Token();
		t.line = line;
		t.col = col;

		if (isCharacter(ch, LETTERS_PATTERN)) {
			readName(t);
			return t;
		} else if (isCharacter(ch, NUMBERS_PATTERN)) {
			try {
				readNumber(t);
			} catch (IntegerOverflowException e) {
				e.printStackTrace();
			}
			return t;
		}else if(ch=='\''){
			readCharCon(t);
		}

		switch (ch) {
		case '.':
			nextCh();
			t.kind = PERIOD;
			break;
		case '[':
			nextCh();
			t.kind = LEFT_BRACKET;
			break;
		case ']':
			nextCh();
			t.kind = RIGHT_BRACKET;
			break;
		case '{':
			nextCh();
			t.kind = LEFT_BRACE;
			break;
		case '}':
			nextCh();
			t.kind = RIGHT_BRACE;
			break;
		case '(':
			nextCh();
			t.kind = LEFT_PARENTHESIS;
			break;
		case ')':
			nextCh();
			t.kind = RIGHT_PARENTHESIS;
			break;
		case '&':
			nextCh();
			if (ch == '&') {
				nextCh();
				t.kind = AND;
			} else {
				t.kind = UNKNOWN;
			}
			break;
		case '|':
			nextCh();
			if (ch == '|') {
				nextCh();
				t.kind = OR;
			} else {
				t.kind = UNKNOWN;
			}
			break;
		case '<':
			nextCh();
			if (ch == '=') {
				nextCh();
				t.kind = LESS_EQUAL;
			} else {
				t.kind = LESS;
			}
			break;
		case '>':
			nextCh();
			if (ch == '=') {
				nextCh();
				t.kind = GREATER_EQUAL;
			} else {
				t.kind = GREATER;
			}
			break;
		case '+':
			nextCh();
			t.kind = PLUS;
			break;
		case '-':
			nextCh();
			t.kind = MINUS;
			break;
		case '*':
			nextCh();
			t.kind = MULTIPLICATION;
			break;
		case '/':
			nextCh();
			if (ch == '/') {
				nextCh();
				do {
					nextCh();
				} while (ch != '\n' && ch != EOFCH);
				t = next();
			} else {
				t.kind = SLASH;
			}
			break;
		case '=':
			nextCh();
			if (ch == '=') {
				nextCh();
				t.kind = EQUAL;
			} else {
				t.kind = ASSIGN;
			}
			break;
		case '!':
			nextCh();
			if (ch == '=') {
				nextCh();
				t.kind = NOT_EQUAL;
			} else {
				t.kind = EXCLAMATION;
			}
			break;
		case ';':
			nextCh();
			t.kind = SEMICOLON;
			break;
		case ',':
			nextCh();
			t.kind = COMMA;
			break;
		case EOFCH:
			t.kind = EOF;
			break;
		}
		return t;
	}

	private static void readName(Token t) {
		StringBuilder name = new StringBuilder();
		do {
			name.append(ch);
			nextCh();
		} while (isCharacter(ch, LETTERS_PATTERN) || isCharacter(ch, NUMBERS_PATTERN));
		String nameValue = name.toString();
		Kind reservedWord = null;
		try {
			reservedWord = Kind.valueOf(nameValue.toUpperCase());
		} catch (Exception e) {
			t.string = nameValue;
			t.kind = Kind.IDENTIFIER;
		}
		if (reservedWord != null) {
			t.kind = reservedWord;
		}
	}

	private static void readNumber(Token t) throws IntegerOverflowException {
		StringBuilder value = new StringBuilder();
		do {
			value.append(ch);
			nextCh();
		} while (isCharacter(ch, NUMBERS_PATTERN));
		try{
			t.val = Integer.parseInt(value.toString());
			t.kind = NUMBER;
		}catch(Exception e){
			throw new IntegerOverflowException("Overflow transformando " + value.toString() + " em inteiro.", e.getCause());
		}
	}
	
	private static void readCharCon(Token t) {
		StringBuilder value = new StringBuilder();
		do {
			value.append(ch);
			nextCh();
		} while (ch!='\'');
		//FIXME - verificar código ascii
		String stringValue = value.toString();
		t.kind = CHARACTER;
		String codeAscii = null;
		//FIXME - melhorar isso aqui
		try {
			codeAscii = new String(stringValue.getBytes("US-ASCII"));
		} catch (UnsupportedEncodingException e) {
			e.printStackTrace();
		}
		if(codeAscii!=null){
			t.val = Integer.valueOf(codeAscii);
		}
		if(!(stringValue.equals("'x'") || stringValue.equals("'\r'") || stringValue.equals("'\n'") || stringValue.equals("'\\'"))){
			System.out.println("Char "+ stringValue +" inválido");
		}
	}
}
